1 Setup

library(magrittr)
library(tidyverse)
library(glue)
library(arrow)
library(matric)
batch <- params$batch
futile.logger::flog.info(glue("Batch = {batch}"))
INFO [2021-05-05 22:13:44] Batch = 2020_11_04_CPJUMP1
experiment <- as.data.frame(params$experiment)
futile.logger::flog.info(glue("Experiment = {experiment}"))
INFO [2021-05-05 22:13:44] Experiment = Standard
INFO [2021-05-05 22:13:44] Experiment = Compound
INFO [2021-05-05 22:13:44] Experiment = U2OS
INFO [2021-05-05 22:13:44] Experiment = 48
data_level <- params$data_level
futile.logger::flog.info(glue("Data level = {data_level}"))
INFO [2021-05-05 22:13:44] Data level = normalized
if (params$normalization == "whole_plate") {
  norm_suffix <- ""
} else if (params$normalization == "negcon") {
  norm_suffix <- "_negcon"
}
sprintf("Normalization = %s. Using suffix = '%s'", params$normalization, norm_suffix)
[1] "Normalization = negcon. Using suffix = '_negcon'"
Normalization = negcon. Using suffix = '_negcon'
filename_prefix_profiles <- glue("{batch}_all_{data_level}{norm_suffix}")

2 Load

parquet_file_recoded <-
  glue("{batch}/{filename_prefix_profiles}_recoded_aggregated.parquet")

if (file.exists(parquet_file_recoded) && !params$force_recompute) {
  parquet_file <- parquet_file_recoded
  
  futile.logger::flog.info(glue("Loading {parquet_file} ..."))
  
  stopifnot(file.exists(parquet_file))
  
  profiles <-
    read_parquet(parquet_file)
  
  variables <-
    str_subset(names(profiles), "Metadata_", negate = TRUE)
  
} else {
  
  parquet_file <-
    glue("../collated/{batch}/{filename_prefix_profiles}.parquet")
  
  futile.logger::flog.info(glue("Loading {parquet_file} ..."))
  
  stopifnot(file.exists(parquet_file))
  
  profiles <-
    read_parquet(parquet_file)
  
  # if(!is.null(experiment)) {
  #   profiles <- profiles %>% inner_join(experiment)
  # }
  
  variables <-
    str_subset(names(profiles), "Metadata_", negate = TRUE)
  
  metadata_cols <-
    str_subset(names(profiles), "Metadata_")
  
  variables <-
    params$variable_groups %>%
    unlist() %>%
    map(function(pattern)
      str_subset(variables, pattern = pattern)) %>%
    unlist()
  
  profiles <-
    profiles %>%
    select(all_of(c(metadata_cols, variables)))
  
  variables <-
    str_subset(names(profiles), "Metadata_", negate = TRUE)
  
  profiles <-
    profiles %>%
    group_by(across(params$strata_replicate)) %>%
    summarize(across(all_of(variables), median),
              .groups = "keep")
  
  attr(profiles, "variable_groups") <- params$variable_groups
  
  parquet_file <-
    parquet_file_recoded
  
  futile.logger::flog.info(glue("Writing {parquet_file} ..."))
  
  profiles %>%
    write_parquet(parquet_file,
                  compression = "gzip",
                  compression_level = 9)
}
INFO [2021-05-05 22:13:44] Loading 2020_11_04_CPJUMP1/2020_11_04_CPJUMP1_all_normalized_negcon_recoded_aggregated.parquet ...

3 Transform

3.1 Functions

get_beta <- function(x = 5, y = 0.99) {
  -log(1 / y - 1) / x
}

abs_logistic <- function(x, beta = get_beta()) {
  2 / (1 + exp(-abs(x) * beta)) - 1
}

cytominer_variable_group_enrichment <-
  function(population,
           variables,
           variable_groups,
           sigmoid_function = abs_logistic,
           ...) {
    
    variables_group_lists <-
      variable_groups %>%
      unlist() %>%
      names() %>%
      set_names() %>%
      purrr::map(function(variable_group) {
        stringr::str_subset(variables, variable_group)
      }) 

    population_data_transformed <-
      variables_group_lists %>%
      map_dfc(function(variables_group_list) {
        population %>%
          dplyr::select(all_of(variables_group_list)) %>%
          dplyr::mutate(across(all_of(variables_group_list),
                               sigmoid_function,
                               ...)) %>%
          dplyr::ungroup() %>%
          dplyr::rowwise() %>%
          dplyr::transmute(n_above = round(sum(
            dplyr::c_across(everything()) /
              length(variables_group_list),
            na.rm = T
          ), 2)) %>%
          dplyr::pull(n_above)
      })
    
    population_metadata <-
      population %>%
      dplyr::select(-all_of(variables))
    
    enriched <-
      dplyr::bind_cols(population_metadata,
                       population_data_transformed)
    
    enriched
    
  }

3.2 Execute

parquet_file <-
  glue("{batch}/{filename_prefix_profiles}_enriched.parquet")

if (file.exists(parquet_file) & !params$force_recompute) {
  futile.logger::flog.info(glue("Reading {parquet_file} ..."))
  
  profiles_enriched <-
    read_parquet(parquet_file)
  
} else {
  
  profiles_enriched <-
    profiles %>%
    group_by(across(params$strata_replicate)) %>%
    summarize(across(all_of(variables), median), .groups = "keep") %>%
    group_by(across(params$nesting_level_0)) %>%
    summarise(
      cytominer_variable_group_enrichment(
        cur_data_all(),
        variables = variables,
        variable_groups = params$variable_groups
      ),
      .groups = "keep"
    ) %>%
    ungroup()
  
  attr(profiles, "variable_groups") <- params$variable_groups
  
  futile.logger::flog.info(glue("Writing {parquet_file} ..."))
  
  profiles_enriched %>%
    write_parquet(parquet_file,
                  compression = "gzip",
                  compression_level = 9)
  
}
INFO [2021-05-05 22:13:49] Reading 2020_11_04_CPJUMP1/2020_11_04_CPJUMP1_all_normalized_negcon_enriched.parquet ...
variables_enriched <-
  str_subset(names(profiles_enriched), "Metadata_", negate = TRUE)

4 Plot

profiles_enriched %>% 
  inner_join(experiment) %>%
  pivot_longer(-matches("Metadata"), 
               names_to = "feature_group")%>% 
  ggplot(aes(feature_group, value)) + 
  geom_boxplot() + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
Joining, by = c("Metadata_experiment_condition", "Metadata_experiment_type", "Metadata_cell_line", "Metadata_timepoint")

profiles_enriched %>% 
  inner_join(experiment) %>%
  pivot_longer(-matches("Metadata"), 
               names_to = "feature_group") %>% 
  ggplot(aes(feature_group, value, group = Metadata_Well)) + 
  geom_line(alpha = .09) + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) + 
  facet_wrap(~Metadata_negcon_or_other, ncol = 1)
Joining, by = c("Metadata_experiment_condition", "Metadata_experiment_type", "Metadata_cell_line", "Metadata_timepoint")

library(plotly)

Attaching package: 'plotly'
The following object is masked from 'package:arrow':

    schema
The following object is masked from 'package:ggplot2':

    last_plot
The following object is masked from 'package:stats':

    filter
The following object is masked from 'package:graphics':

    layout
df <- 
  profiles_enriched %>%
  inner_join(experiment)
Joining, by = c("Metadata_experiment_condition", "Metadata_experiment_type", "Metadata_cell_line", "Metadata_timepoint")
dimensions <- list(
  list(label = "Intensity_RNA",
       values = ~ Intensity_RNA),
  list(label = 'Intensity_DNA',
       values = ~ Intensity_DNA)
)

dimensions <-
  variables_enriched %>%
  map(function(variable) {
    list(label = variable,
         values = df[[variable]])
  })

df %>%
  plot_ly(width = 1000, 
          height = 600) %>%
  add_trace(type = 'parcoords',
            dimensions = dimensions)
LS0tCnRpdGxlOiAiRmVhdHVyZSBhbmFseXNpcyIKcGFyYW1zOgogIGZvcmNlX3JlY29tcHV0ZTogRkFMU0UKICBiYXRjaDogMjAyMF8xMV8wNF9DUEpVTVAxCiAgZGF0YV9sZXZlbDogbm9ybWFsaXplZAogIG5vcm1hbGl6YXRpb246IG5lZ2NvbgogIGV4cGVyaW1lbnQ6IAogICAgdmFsdWU6CiAgICAgIE1ldGFkYXRhX2V4cGVyaW1lbnRfY29uZGl0aW9uOiBTdGFuZGFyZAogICAgICBNZXRhZGF0YV9leHBlcmltZW50X3R5cGU6IENvbXBvdW5kCiAgICAgIE1ldGFkYXRhX2NlbGxfbGluZTogVTJPUwogICAgICBNZXRhZGF0YV90aW1lcG9pbnQ6ICI0OCIKICBuZXN0aW5nX2xldmVsXzA6CiAgICB2YWx1ZToKICAgICAgLSBNZXRhZGF0YV9leHBlcmltZW50X2NvbmRpdGlvbgogICAgICAtIE1ldGFkYXRhX2V4cGVyaW1lbnRfdHlwZQogICAgICAtIE1ldGFkYXRhX2NlbGxfbGluZQogICAgICAtIE1ldGFkYXRhX3RpbWVwb2ludAogIHN0cmF0YV9yZXBsaWNhdGU6CiAgICB2YWx1ZToKICAgICAgLSBNZXRhZGF0YV9leHBlcmltZW50X2NvbmRpdGlvbgogICAgICAtIE1ldGFkYXRhX2V4cGVyaW1lbnRfdHlwZQogICAgICAtIE1ldGFkYXRhX2NlbGxfbGluZQogICAgICAtIE1ldGFkYXRhX3RpbWVwb2ludAogICAgICAtIE1ldGFkYXRhX3BsYXRlX21hcF9uYW1lCiAgICAgIC0gTWV0YWRhdGFfV2VsbAogICAgICAtIE1ldGFkYXRhX2dlbmVzCiAgICAgIC0gTWV0YWRhdGFfcGVydF90eXBlCiAgICAgIC0gTWV0YWRhdGFfY29udHJvbF90eXBlCiAgICAgIC0gTWV0YWRhdGFfUGxhdGVfTWFwX05hbWUKICAgICAgLSBNZXRhZGF0YV9uZWdjb25fY29udHJvbF90eXBlCiAgICAgIC0gTWV0YWRhdGFfdGFyZ2V0X3NlcXVlbmNlCiAgICAgIC0gTWV0YWRhdGFfbWdfcGVyX21sCiAgICAgIC0gTWV0YWRhdGFfbW1vbGVzX3Blcl9saXRlcgogICAgICAtIE1ldGFkYXRhX3NvbHZlbnQKICAgICAgLSBNZXRhZGF0YV90YXJnZXQKICAgICAgLSBNZXRhZGF0YV9wZXJ0X2luYW1lCiAgICAgIC0gTWV0YWRhdGFfYnJvYWRfc2FtcGxlCiAgICAgIC0gTWV0YWRhdGFfcHViY2hlbV9jaWQKICAgICAgLSBNZXRhZGF0YV9JbkNoSUtleQogICAgICAtIE1ldGFkYXRhX2dlbmUKICAgICAgLSBNZXRhZGF0YV9uZWdjb25fb3Jfb3RoZXIKICAgICAgLSBNZXRhZGF0YV9uZWdjb25fY29udHJvbF90eXBlX3RyaW1tZWQKICB2YXJpYWJsZV9ncm91cHM6CiAgICB2YWx1ZToKICAgICAgLSB4QXJlYTogX0FyZWFTaGFwZV9BcmVhJAogICAgICAtIHhTaGFwZTogQXJlYVNoYXBlCiAgICAgIC0geE5laWdoOiBOZWlnaGJvcnMKICAgICAgLSB4Q29ycjogQ29ycmVsYXRpb24KICAgICAgLSBUZXhfQUdQOiAoKFRleHR1cmV8R3JhbnVsYXJpdHl8UmFkaWFsRGlzdHJpYnV0aW9uKV8uKl8oQUdQKSkKICAgICAgLSBUZXhfRE5BOiAoKFRleHR1cmV8R3JhbnVsYXJpdHl8UmFkaWFsRGlzdHJpYnV0aW9uKV8uKl8oRE5BKSkKICAgICAgLSBUZXhfRVI6ICgoVGV4dHVyZXxHcmFudWxhcml0eXxSYWRpYWxEaXN0cmlidXRpb24pXy4qXyhFUikpCiAgICAgIC0gVGV4X01pdG86ICgoVGV4dHVyZXxHcmFudWxhcml0eXxSYWRpYWxEaXN0cmlidXRpb24pXy4qXyhNaXRvKSkKICAgICAgLSBUZXhfUk5BOiAoKFRleHR1cmV8R3JhbnVsYXJpdHl8UmFkaWFsRGlzdHJpYnV0aW9uKV8uKl8oUk5BKSkKICAgICAgLSBJbnRfQUdQOiAoKEludGVuc2l0eSlfLipfKEFHUCkpCiAgICAgIC0gSW50X0ROQTogKChJbnRlbnNpdHkpXy4qXyhETkEpKQogICAgICAtIEludF9FUjogKChJbnRlbnNpdHkpXy4qXyhFUikpCiAgICAgIC0gSW50X01pdG86ICgoSW50ZW5zaXR5KV8uKl8oTWl0bykpCiAgICAgIC0gSW50X1JOQTogKChJbnRlbnNpdHkpXy4qXyhSTkEpKQotLS0KCiMgU2V0dXAKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdsdWUpCmxpYnJhcnkoYXJyb3cpCmxpYnJhcnkobWF0cmljKQpgYGAKCgpgYGB7cn0KYmF0Y2ggPC0gcGFyYW1zJGJhdGNoCmZ1dGlsZS5sb2dnZXI6OmZsb2cuaW5mbyhnbHVlKCJCYXRjaCA9IHtiYXRjaH0iKSkKYGBgCmBgYHtyfQpleHBlcmltZW50IDwtIGFzLmRhdGEuZnJhbWUocGFyYW1zJGV4cGVyaW1lbnQpCmZ1dGlsZS5sb2dnZXI6OmZsb2cuaW5mbyhnbHVlKCJFeHBlcmltZW50ID0ge2V4cGVyaW1lbnR9IikpCmBgYAoKCmBgYHtyfQpkYXRhX2xldmVsIDwtIHBhcmFtcyRkYXRhX2xldmVsCmZ1dGlsZS5sb2dnZXI6OmZsb2cuaW5mbyhnbHVlKCJEYXRhIGxldmVsID0ge2RhdGFfbGV2ZWx9IikpCmBgYAoKCmBgYHtyfQppZiAocGFyYW1zJG5vcm1hbGl6YXRpb24gPT0gIndob2xlX3BsYXRlIikgewogIG5vcm1fc3VmZml4IDwtICIiCn0gZWxzZSBpZiAocGFyYW1zJG5vcm1hbGl6YXRpb24gPT0gIm5lZ2NvbiIpIHsKICBub3JtX3N1ZmZpeCA8LSAiX25lZ2NvbiIKfQpzcHJpbnRmKCJOb3JtYWxpemF0aW9uID0gJXMuIFVzaW5nIHN1ZmZpeCA9ICclcyciLCBwYXJhbXMkbm9ybWFsaXphdGlvbiwgbm9ybV9zdWZmaXgpCmBgYAoKCmBgYHtyfQpmaWxlbmFtZV9wcmVmaXhfcHJvZmlsZXMgPC0gZ2x1ZSgie2JhdGNofV9hbGxfe2RhdGFfbGV2ZWx9e25vcm1fc3VmZml4fSIpCmBgYAoKIyBMb2FkCgpgYGB7cn0KcGFycXVldF9maWxlX3JlY29kZWQgPC0KICBnbHVlKCJ7YmF0Y2h9L3tmaWxlbmFtZV9wcmVmaXhfcHJvZmlsZXN9X3JlY29kZWRfYWdncmVnYXRlZC5wYXJxdWV0IikKCmlmIChmaWxlLmV4aXN0cyhwYXJxdWV0X2ZpbGVfcmVjb2RlZCkgJiYgIXBhcmFtcyRmb3JjZV9yZWNvbXB1dGUpIHsKICBwYXJxdWV0X2ZpbGUgPC0gcGFycXVldF9maWxlX3JlY29kZWQKICAKICBmdXRpbGUubG9nZ2VyOjpmbG9nLmluZm8oZ2x1ZSgiTG9hZGluZyB7cGFycXVldF9maWxlfSAuLi4iKSkKICAKICBzdG9waWZub3QoZmlsZS5leGlzdHMocGFycXVldF9maWxlKSkKICAKICBwcm9maWxlcyA8LQogICAgcmVhZF9wYXJxdWV0KHBhcnF1ZXRfZmlsZSkKICAKICB2YXJpYWJsZXMgPC0KICAgIHN0cl9zdWJzZXQobmFtZXMocHJvZmlsZXMpLCAiTWV0YWRhdGFfIiwgbmVnYXRlID0gVFJVRSkKICAKfSBlbHNlIHsKICAKICBwYXJxdWV0X2ZpbGUgPC0KICAgIGdsdWUoIi4uL2NvbGxhdGVkL3tiYXRjaH0ve2ZpbGVuYW1lX3ByZWZpeF9wcm9maWxlc30ucGFycXVldCIpCiAgCiAgZnV0aWxlLmxvZ2dlcjo6ZmxvZy5pbmZvKGdsdWUoIkxvYWRpbmcge3BhcnF1ZXRfZmlsZX0gLi4uIikpCiAgCiAgc3RvcGlmbm90KGZpbGUuZXhpc3RzKHBhcnF1ZXRfZmlsZSkpCiAgCiAgcHJvZmlsZXMgPC0KICAgIHJlYWRfcGFycXVldChwYXJxdWV0X2ZpbGUpCiAgCiAgIyBpZighaXMubnVsbChleHBlcmltZW50KSkgewogICMgICBwcm9maWxlcyA8LSBwcm9maWxlcyAlPiUgaW5uZXJfam9pbihleHBlcmltZW50KQogICMgfQogIAogIHZhcmlhYmxlcyA8LQogICAgc3RyX3N1YnNldChuYW1lcyhwcm9maWxlcyksICJNZXRhZGF0YV8iLCBuZWdhdGUgPSBUUlVFKQogIAogIG1ldGFkYXRhX2NvbHMgPC0KICAgIHN0cl9zdWJzZXQobmFtZXMocHJvZmlsZXMpLCAiTWV0YWRhdGFfIikKICAKICB2YXJpYWJsZXMgPC0KICAgIHBhcmFtcyR2YXJpYWJsZV9ncm91cHMgJT4lCiAgICB1bmxpc3QoKSAlPiUKICAgIG1hcChmdW5jdGlvbihwYXR0ZXJuKQogICAgICBzdHJfc3Vic2V0KHZhcmlhYmxlcywgcGF0dGVybiA9IHBhdHRlcm4pKSAlPiUKICAgIHVubGlzdCgpCiAgCiAgcHJvZmlsZXMgPC0KICAgIHByb2ZpbGVzICU+JQogICAgc2VsZWN0KGFsbF9vZihjKG1ldGFkYXRhX2NvbHMsIHZhcmlhYmxlcykpKQogIAogIHZhcmlhYmxlcyA8LQogICAgc3RyX3N1YnNldChuYW1lcyhwcm9maWxlcyksICJNZXRhZGF0YV8iLCBuZWdhdGUgPSBUUlVFKQogIAogIHByb2ZpbGVzIDwtCiAgICBwcm9maWxlcyAlPiUKICAgIGdyb3VwX2J5KGFjcm9zcyhwYXJhbXMkc3RyYXRhX3JlcGxpY2F0ZSkpICU+JQogICAgc3VtbWFyaXplKGFjcm9zcyhhbGxfb2YodmFyaWFibGVzKSwgbWVkaWFuKSwKICAgICAgICAgICAgICAuZ3JvdXBzID0gImtlZXAiKQogIAogIGF0dHIocHJvZmlsZXMsICJ2YXJpYWJsZV9ncm91cHMiKSA8LSBwYXJhbXMkdmFyaWFibGVfZ3JvdXBzCiAgCiAgcGFycXVldF9maWxlIDwtCiAgICBwYXJxdWV0X2ZpbGVfcmVjb2RlZAogIAogIGZ1dGlsZS5sb2dnZXI6OmZsb2cuaW5mbyhnbHVlKCJXcml0aW5nIHtwYXJxdWV0X2ZpbGV9IC4uLiIpKQogIAogIHByb2ZpbGVzICU+JQogICAgd3JpdGVfcGFycXVldChwYXJxdWV0X2ZpbGUsCiAgICAgICAgICAgICAgICAgIGNvbXByZXNzaW9uID0gImd6aXAiLAogICAgICAgICAgICAgICAgICBjb21wcmVzc2lvbl9sZXZlbCA9IDkpCn0KYGBgCgojIFRyYW5zZm9ybQoKIyMgRnVuY3Rpb25zCgpgYGB7cn0KZ2V0X2JldGEgPC0gZnVuY3Rpb24oeCA9IDUsIHkgPSAwLjk5KSB7CiAgLWxvZygxIC8geSAtIDEpIC8geAp9CgphYnNfbG9naXN0aWMgPC0gZnVuY3Rpb24oeCwgYmV0YSA9IGdldF9iZXRhKCkpIHsKICAyIC8gKDEgKyBleHAoLWFicyh4KSAqIGJldGEpKSAtIDEKfQoKY3l0b21pbmVyX3ZhcmlhYmxlX2dyb3VwX2VucmljaG1lbnQgPC0KICBmdW5jdGlvbihwb3B1bGF0aW9uLAogICAgICAgICAgIHZhcmlhYmxlcywKICAgICAgICAgICB2YXJpYWJsZV9ncm91cHMsCiAgICAgICAgICAgc2lnbW9pZF9mdW5jdGlvbiA9IGFic19sb2dpc3RpYywKICAgICAgICAgICAuLi4pIHsKICAgIAogICAgdmFyaWFibGVzX2dyb3VwX2xpc3RzIDwtCiAgICAgIHZhcmlhYmxlX2dyb3VwcyAlPiUKICAgICAgdW5saXN0KCkgJT4lCiAgICAgIG5hbWVzKCkgJT4lCiAgICAgIHNldF9uYW1lcygpICU+JQogICAgICBwdXJycjo6bWFwKGZ1bmN0aW9uKHZhcmlhYmxlX2dyb3VwKSB7CiAgICAgICAgc3RyaW5ncjo6c3RyX3N1YnNldCh2YXJpYWJsZXMsIHZhcmlhYmxlX2dyb3VwKQogICAgICB9KSAKCiAgICBwb3B1bGF0aW9uX2RhdGFfdHJhbnNmb3JtZWQgPC0KICAgICAgdmFyaWFibGVzX2dyb3VwX2xpc3RzICU+JQogICAgICBtYXBfZGZjKGZ1bmN0aW9uKHZhcmlhYmxlc19ncm91cF9saXN0KSB7CiAgICAgICAgcG9wdWxhdGlvbiAlPiUKICAgICAgICAgIGRwbHlyOjpzZWxlY3QoYWxsX29mKHZhcmlhYmxlc19ncm91cF9saXN0KSkgJT4lCiAgICAgICAgICBkcGx5cjo6bXV0YXRlKGFjcm9zcyhhbGxfb2YodmFyaWFibGVzX2dyb3VwX2xpc3QpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2lnbW9pZF9mdW5jdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4uLikpICU+JQogICAgICAgICAgZHBseXI6OnVuZ3JvdXAoKSAlPiUKICAgICAgICAgIGRwbHlyOjpyb3d3aXNlKCkgJT4lCiAgICAgICAgICBkcGx5cjo6dHJhbnNtdXRlKG5fYWJvdmUgPSByb3VuZChzdW0oCiAgICAgICAgICAgIGRwbHlyOjpjX2Fjcm9zcyhldmVyeXRoaW5nKCkpIC8KICAgICAgICAgICAgICBsZW5ndGgodmFyaWFibGVzX2dyb3VwX2xpc3QpLAogICAgICAgICAgICBuYS5ybSA9IFQKICAgICAgICAgICksIDIpKSAlPiUKICAgICAgICAgIGRwbHlyOjpwdWxsKG5fYWJvdmUpCiAgICAgIH0pCiAgICAKICAgIHBvcHVsYXRpb25fbWV0YWRhdGEgPC0KICAgICAgcG9wdWxhdGlvbiAlPiUKICAgICAgZHBseXI6OnNlbGVjdCgtYWxsX29mKHZhcmlhYmxlcykpCiAgICAKICAgIGVucmljaGVkIDwtCiAgICAgIGRwbHlyOjpiaW5kX2NvbHMocG9wdWxhdGlvbl9tZXRhZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICBwb3B1bGF0aW9uX2RhdGFfdHJhbnNmb3JtZWQpCiAgICAKICAgIGVucmljaGVkCiAgICAKICB9CmBgYAoKIyMgRXhlY3V0ZQoKYGBge3J9CnBhcnF1ZXRfZmlsZSA8LQogIGdsdWUoIntiYXRjaH0ve2ZpbGVuYW1lX3ByZWZpeF9wcm9maWxlc31fZW5yaWNoZWQucGFycXVldCIpCgppZiAoZmlsZS5leGlzdHMocGFycXVldF9maWxlKSAmICFwYXJhbXMkZm9yY2VfcmVjb21wdXRlKSB7CiAgZnV0aWxlLmxvZ2dlcjo6ZmxvZy5pbmZvKGdsdWUoIlJlYWRpbmcge3BhcnF1ZXRfZmlsZX0gLi4uIikpCiAgCiAgcHJvZmlsZXNfZW5yaWNoZWQgPC0KICAgIHJlYWRfcGFycXVldChwYXJxdWV0X2ZpbGUpCiAgCn0gZWxzZSB7CiAgCiAgcHJvZmlsZXNfZW5yaWNoZWQgPC0KICAgIHByb2ZpbGVzICU+JQogICAgZ3JvdXBfYnkoYWNyb3NzKHBhcmFtcyRzdHJhdGFfcmVwbGljYXRlKSkgJT4lCiAgICBzdW1tYXJpemUoYWNyb3NzKGFsbF9vZih2YXJpYWJsZXMpLCBtZWRpYW4pLCAuZ3JvdXBzID0gImtlZXAiKSAlPiUKICAgIGdyb3VwX2J5KGFjcm9zcyhwYXJhbXMkbmVzdGluZ19sZXZlbF8wKSkgJT4lCiAgICBzdW1tYXJpc2UoCiAgICAgIGN5dG9taW5lcl92YXJpYWJsZV9ncm91cF9lbnJpY2htZW50KAogICAgICAgIGN1cl9kYXRhX2FsbCgpLAogICAgICAgIHZhcmlhYmxlcyA9IHZhcmlhYmxlcywKICAgICAgICB2YXJpYWJsZV9ncm91cHMgPSBwYXJhbXMkdmFyaWFibGVfZ3JvdXBzCiAgICAgICksCiAgICAgIC5ncm91cHMgPSAia2VlcCIKICAgICkgJT4lCiAgICB1bmdyb3VwKCkKICAKICBhdHRyKHByb2ZpbGVzLCAidmFyaWFibGVfZ3JvdXBzIikgPC0gcGFyYW1zJHZhcmlhYmxlX2dyb3VwcwogIAogIGZ1dGlsZS5sb2dnZXI6OmZsb2cuaW5mbyhnbHVlKCJXcml0aW5nIHtwYXJxdWV0X2ZpbGV9IC4uLiIpKQogIAogIHByb2ZpbGVzX2VucmljaGVkICU+JQogICAgd3JpdGVfcGFycXVldChwYXJxdWV0X2ZpbGUsCiAgICAgICAgICAgICAgICAgIGNvbXByZXNzaW9uID0gImd6aXAiLAogICAgICAgICAgICAgICAgICBjb21wcmVzc2lvbl9sZXZlbCA9IDkpCiAgCn0KCnZhcmlhYmxlc19lbnJpY2hlZCA8LQogIHN0cl9zdWJzZXQobmFtZXMocHJvZmlsZXNfZW5yaWNoZWQpLCAiTWV0YWRhdGFfIiwgbmVnYXRlID0gVFJVRSkKYGBgCgojIFBsb3QKCmBgYHtyfQpwcm9maWxlc19lbnJpY2hlZCAlPiUgCiAgaW5uZXJfam9pbihleHBlcmltZW50KSAlPiUKICBwaXZvdF9sb25nZXIoLW1hdGNoZXMoIk1ldGFkYXRhIiksIAogICAgICAgICAgICAgICBuYW1lc190byA9ICJmZWF0dXJlX2dyb3VwIiklPiUgCiAgZ2dwbG90KGFlcyhmZWF0dXJlX2dyb3VwLCB2YWx1ZSkpICsgCiAgZ2VvbV9ib3hwbG90KCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpCmBgYAoKYGBge3J9CnByb2ZpbGVzX2VucmljaGVkICU+JSAKICBpbm5lcl9qb2luKGV4cGVyaW1lbnQpICU+JQogIHBpdm90X2xvbmdlcigtbWF0Y2hlcygiTWV0YWRhdGEiKSwgCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gImZlYXR1cmVfZ3JvdXAiKSAlPiUgCiAgZ2dwbG90KGFlcyhmZWF0dXJlX2dyb3VwLCB2YWx1ZSwgZ3JvdXAgPSBNZXRhZGF0YV9XZWxsKSkgKyAKICBnZW9tX2xpbmUoYWxwaGEgPSAuMDkpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKSArIAogIGZhY2V0X3dyYXAofk1ldGFkYXRhX25lZ2Nvbl9vcl9vdGhlciwgbmNvbCA9IDEpCmBgYApgYGB7ciBldmFsPVRSVUV9CmxpYnJhcnkocGxvdGx5KQoKZGYgPC0gCiAgcHJvZmlsZXNfZW5yaWNoZWQgJT4lCiAgaW5uZXJfam9pbihleHBlcmltZW50KQoKZGltZW5zaW9ucyA8LSBsaXN0KAogIGxpc3QobGFiZWwgPSAiSW50ZW5zaXR5X1JOQSIsCiAgICAgICB2YWx1ZXMgPSB+IEludGVuc2l0eV9STkEpLAogIGxpc3QobGFiZWwgPSAnSW50ZW5zaXR5X0ROQScsCiAgICAgICB2YWx1ZXMgPSB+IEludGVuc2l0eV9ETkEpCikKCmRpbWVuc2lvbnMgPC0KICB2YXJpYWJsZXNfZW5yaWNoZWQgJT4lCiAgbWFwKGZ1bmN0aW9uKHZhcmlhYmxlKSB7CiAgICBsaXN0KGxhYmVsID0gdmFyaWFibGUsCiAgICAgICAgIHZhbHVlcyA9IGRmW1t2YXJpYWJsZV1dKQogIH0pCgpkZiAlPiUKICBwbG90X2x5KHdpZHRoID0gMTAwMCwgCiAgICAgICAgICBoZWlnaHQgPSA2MDApICU+JQogIGFkZF90cmFjZSh0eXBlID0gJ3BhcmNvb3JkcycsCiAgICAgICAgICAgIGRpbWVuc2lvbnMgPSBkaW1lbnNpb25zKQpgYGAKCgoKCg==